use std::{collections::HashMap, cell::RefCell, rc::Rc, time::{SystemTimeError, SystemTime, UNIX_EPOCH}, sync::Arc};

use crate::{native, rtda::{Frame, heap::{ArrayObject, string_pool}, Object, OperandStack}, instructions::base};

static JL_SYSTEM: &'static str = "java/lang/System";

pub fn init() {
    native::register(JL_SYSTEM, "arraycopy", "(Ljava/lang/Object;ILjava/lang/Object;II)V", arraycopy);
    native::register(JL_SYSTEM, "initProperties", "(Ljava/util/Properties;)Ljava/util/Properties;", init_properties);
	native::register(JL_SYSTEM, "setIn0", "(Ljava/io/InputStream;)V", set_in0);
    native::register(JL_SYSTEM, "setOut0", "(Ljava/io/PrintStream;)V", set_out0);
    native::register(JL_SYSTEM, "setErr0", "(Ljava/io/PrintStream;)V", set_err0);
	native::register(JL_SYSTEM, "currentTimeMillis", "()J", current_time_millis);
	native::register(JL_SYSTEM, "nanoTime", "()J", nano_time);
}

// public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
// (Ljava/lang/Object;ILjava/lang/Object;II)V
fn arraycopy(frame: Rc<RefCell<Frame>>) {
    let vars = frame.borrow().get_local_vars();
    let src = vars.borrow().get_ref(0);
    let src_pos = vars.borrow().get_int(1);
    let dest = vars.borrow().get_ref(2);
    let dest_pos = vars.borrow().get_int(3);
    let length = vars.borrow().get_int(4);
    // 源数组和目标数组不能是null
    match (src, dest) {
        (Some(src), Some(dest)) => {
            if !check_array_copy(src, dest) {
                panic!("java.lang.ArrayStoreException")
            }
            if src_pos < 0 || dest_pos < 0 || length < 0
            || src_pos + length > unsafe { &*src }.array_length() as i32
            || dest_pos + length > unsafe { &*dest }.array_length() as i32 {
                panic!("java.lang.IndexOutOfBoundsException")
            }
            ArrayObject::array_copy(src, dest, src_pos as usize, dest_pos as usize, length as usize);
        },
        _ => panic!("java.lang.NullPointerException")
    }
}

fn check_array_copy(src: *mut Object, dest: *mut Object) -> bool {
    let src_class = unsafe { &*src }.get_class();
    let dest_class = unsafe { &*dest }.get_class();
    if !src_class.is_array() || !dest_class.is_array() {
        return false;
    }
    if src_class.component_class().is_primitive()
    || dest_class.component_class().is_primitive() {
        return Arc::ptr_eq(&src_class, &dest_class);
    }
    
    true
}

// private static native Properties initProperties(Properties props);
// (Ljava/util/Properties;)Ljava/util/Properties;
fn init_properties(frame: Rc<RefCell<Frame>>) {
    let vars = frame.borrow().get_local_vars();
    let props = vars.borrow().get_ref(0).unwrap();

    let stack = frame.borrow().get_operand_stack();
    stack.borrow_mut().push_ref(Some(props));

    // public synchronized Object setProperty(String key, String value)
    let set_prop_method = unsafe { &*{ &*props }.get_class() }.get_instance_method("setProperty", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;");
    let thread = frame.borrow().get_thread();

    let map: HashMap<&str, &str> = HashMap::from_iter(
        vec![
            ("java.version",         "1.8.0"),
            ("java.vendor",          "jvm.go"),
            ("java.vendor.url",      "https://github.com/zxh0/jvm.go"),
            ("java.home",            "todo"),
            ("java.class.version",   "52.0"),
            ("java.class.path",      "todo"),
            ("java.awt.graphicsenv", "sun.awt.CGraphicsEnvironment"),
            ("os.name",              "runtime.GOOS"),   // todo
            ("os.arch",              "runtime.GOARCH"), // todo
            ("os.version",           ""),             // todo
            ("file.separator",       "/"),            // todo os.PathSeparator
            ("path.separator",       ":"),            // todo os.PathListSeparator
            ("line.separator",       "\n"),           // todo
            ("user.name",            ""),             // todo
            ("user.home",            ""),             // todo
            ("user.dir",             "."),            // todo
            ("user.country",         "CN"),           // todo
            ("file.encoding",        "UTF-8"),
            ("sun.stdout.encoding",  "UTF-8"),
            ("sun.stderr.encoding",  "UTF-8"),
        ].into_iter()
    );

    let class_loader = frame.borrow().get_method().get_class().get_class_loader().unwrap();
    let mut class_loader = class_loader.lock().unwrap();
    for (key, val) in map.into_iter() {
        let j_key = string_pool::j_string(&mut class_loader, key);
        let j_val = string_pool::j_string(&mut class_loader, val);
        let mut ops = OperandStack::new_operand_stack(3);
        ops.push_ref(Some(props));
        ops.push_ref(Some(j_key));
        ops.push_ref(Some(j_val));

        let shim_frame = Frame::new_shim_frame(thread.clone(), Rc::new(RefCell::new(ops)));
        thread.lock().unwrap().push_frame(shim_frame);
        let shim_frame = thread.lock().unwrap().current_frame();
        base::invoke_method(shim_frame, set_prop_method.clone().unwrap());
    }



}

// fn _sys_props() -> HashMap<String, String> {
//     HashMap::from_iter(
//         vec![
//             ("void",    "V"),
//             ("boolean", "Z"),
//             ("byte",    "B"),
//             ("short" ,  "S"),
//             ("int",     "I"),
//             ("long",    "J"),
//             ("char",    "C"),
//             ("float",   "F"),
//             ("double",  "D"),
//         ].into_iter()
//     )
// }

// private static native void setIn0(InputStream in);
// (Ljava/io/InputStream;)V
fn set_in0(frame: Rc<RefCell<Frame>>) {
    let vars = frame.borrow().get_local_vars();
    let _in = vars.borrow().get_ref(0);

    let sys_class = frame.borrow().get_method().get_class_ptr();
    sys_class.set_ref_var("in", "Ljava/io/InputStream;", _in.unwrap());
}

// private static native void setOut0(PrintStream out);
// (Ljava/io/PrintStream;)V
fn set_out0(frame: Rc<RefCell<Frame>>) {
    let vars = frame.borrow().get_local_vars();
    let out = vars.borrow().get_ref(0);

    let sys_class = frame.borrow().get_method().get_class_ptr();
    sys_class.set_ref_var("out", "Ljava/io/PrintStream;", out.unwrap());
}

// private static native void setErr0(PrintStream err);
// (Ljava/io/PrintStream;)V
fn set_err0(frame: Rc<RefCell<Frame>>) {
    let vars = frame.borrow().get_local_vars();
    let err = vars.borrow().get_ref(0);

    let sys_class = frame.borrow().get_method().get_class_ptr();
    sys_class.set_ref_var("err", "Ljava/io/PrintStream;", err.unwrap());
}

// public static native long currentTimeMillis();
// ()J
fn current_time_millis(frame: Rc<RefCell<Frame>>) {
    if let Ok(time_millis) = get_time_millis() {
        let stack = frame.borrow().get_operand_stack();
        stack.borrow_mut().push_long(time_millis);
    }
}

fn get_time_millis() -> Result<i64, SystemTimeError> {
    let now = SystemTime::now();
    let epoch = UNIX_EPOCH;

    // Convert SystemTime to Duration and then to milliseconds
    let duration = now.duration_since(epoch)?;
    let milliseconds = duration.as_millis() as i64;

    Ok(milliseconds)
}

// public static native long nanoTime();
// ()J
fn nano_time(frame: Rc<RefCell<Frame>>) {
    if let Ok(time_millis) = get_time_nanos() {
        let stack = frame.borrow().get_operand_stack();
        stack.borrow_mut().push_long(time_millis);
    }
}

fn get_time_nanos() -> Result<i64, SystemTimeError> {
    let now = SystemTime::now();
    let epoch = UNIX_EPOCH;

    // Convert SystemTime to Duration and then to milliseconds
    let duration = now.duration_since(epoch)?;
    let nanos = duration.as_nanos() as i64;

    Ok(nanos)
}